<?php

class Pipeline {

	// 要被处理的对象
	protected $passable;

	// 方法
	protected $pipes = [];

	public function send($passable) {    
	    $this->passable = $passable;    
	    return $this;
	}

	/**
     * Get the array of configured pipes.
     *
     * @return array
     */
    protected function pipes()
    {
        return $this->pipes;
    }


    /**
     * Run the pipeline with a final destination callback.
     *
     * @param  \Closure  $destination
     * @return mixed
     */
    public function then(Closure $destination)
    {
        $pipeline = array_reduce(
            array_reverse($this->pipes()), $this->carry(), $this->prepareDestination($destination)
        );

        return $pipeline($this->passable);
    }

	/**
     * Set the array of pipes.
     *
     * @param  array|mixed  $pipes
     * @return $this
     */
    public function through($pipes)
    {
        $this->pipes = is_array($pipes) ? $pipes : func_get_args();

        return $this;
    }


    /**
     * Parse full pipe string to get name and parameters.
     *
     * @param  string $pipe
     * @return array
     */
    protected function parsePipeString($pipe)
    {
        [$name, $parameters] = array_pad(explode(':', $pipe, 2), 2, []);

        if (is_string($parameters)) {
            $parameters = explode(',', $parameters);
        }

        return [$name, $parameters];
    }

    /**
     * Get the final piece of the Closure onion.
     *
     * @param  \Closure  $destination
     * @return \Closure
     */
    protected function prepareDestination(Closure $destination)
    {
        return function ($passable) use ($destination) {
            try {
                return $destination($passable);
            } catch (Exception $e) {
                return $this->handleException($passable, $e);
            } catch (Throwable $e) {
                return $this->handleException($passable, new FatalThrowableError($e));
            }
        };
    }

    /**
     * 处理从每个管道返回的值，然后将其传递给下一个管道。
     * Handles the value returned from each pipe before passing it to the next.
     *
     * @param  mixed $carry
     * @return mixed
     */
    protected function handleCarry($carry)
    {
        return $carry;
    }

    /**
     * Handle the given exception.
     *
     * @param  mixed  $passable
     * @param  \Exception  $e
     * @return mixed
     *
     * @throws \Exception
     */
    protected function handleException($passable, Exception $e)
    {
        throw $e;
    }

    protected function carry()
    {
    	return function ($stack, $pipe) {
            return function ($passable) use ($stack, $pipe) {
                try {
                    if (is_callable($pipe)) {
                        // If the pipe is an instance of a Closure, we will just call it directly but
                        // otherwise we'll resolve the pipes out of the container and call it with
                        // the appropriate method and arguments, returning the results back out.
                        return $pipe($passable, $stack);
                    } else {
                        // If the pipe is already an object we'll just make a callable and pass it to
                        // the pipe as-is. There is no need to do any extra parsing and formatting
                        // since the object we're given was already a fully instantiated object.
                        $parameters = [$passable, $stack];
                    }

                    $carry = method_exists($pipe, $this->method)
                                    ? $pipe->{$this->method}(...$parameters)
                                    : $pipe(...$parameters);

                    return $this->handleCarry($carry);
                } catch (Exception $e) {
                    return $this->handleException($passable, $e);
                } catch (Throwable $e) {
                    return $this->handleException($passable, $e);
                }
            };
        };
    }

}

$poster = 5;

$pipe1 = function ($poster, \Closure $next) {
    $poster += 1;
    echo "pipe1: $poster\n";
    return $next($poster);
};

$pipe2 = function ($poster, \Closure $next) {
    if ($poster > 7) {
        return $poster;
    }

    $poster += 3;
    echo "pipe2: $poster\n";
    return $next($poster);
};

$pipe3 = function ($poster, \Closure $next) {
    $result = $next($poster);
    echo "pipe3: $result\n";
    return $result * 2;
};

$pipe4 = function ($poster, \Closure $next) {
    $poster += 2;
    echo "pipe4 : $poster\n";
    return $next($poster);
};

$pipes = [$pipe1, $pipe2, $pipe3, $pipe4];

(new Pipeline)->send($poster)->through($pipes)->then(function ($poster) {
    echo "received: $poster\n";
    return 3;
}) . "\n";





